﻿
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Workflow.Runtime.Hosting;
using System.Workflow.ComponentModel;
using System.Workflow.Runtime;
using System.Transactions;
using System.Collections;
using System.IO;
using System.Diagnostics;
using System.Threading;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Workflow.ComponentModel.Serialization;

namespace TextFlow.Engine
{
    public class InMemoryPersistenceService : WorkflowPersistenceService
    {
        private Dictionary<Guid, byte[]> _workflows = new Dictionary<Guid, byte[]>();
        private Dictionary<Guid, Dictionary<Guid, byte[]>> _scopes = new Dictionary<Guid, Dictionary<Guid, byte[]>>();
        private Dictionary<Guid, Timer> _instanceTimers = new Dictionary<Guid, Timer>();

        public readonly static TimeSpan MaxInterval = new TimeSpan( 30, 0, 0, 0 );

        public InMemoryPersistenceService()
        {
        }

        protected override Activity LoadCompletedContextActivity( Guid scope, Activity outerActivity )
        {
            Debug.Assert( _scopes != null );

            Dictionary<Guid, byte[]> scopes = null;

            if ( _scopes.ContainsKey( WorkflowEnvironment.WorkflowInstanceId ) == false )
            {
                throw new PersistenceException();
            }
            else
            {
                scopes = _scopes[ WorkflowEnvironment.WorkflowInstanceId ];
            }

            Activity deserialized = Load( scopes[ scope ], outerActivity );

            Debug.Assert( deserialized != null );

            return deserialized;
        }

        protected override void SaveCompletedContextActivity( Activity activity )
        {
            Guid scope = (Guid) activity.GetValue( Activity.ActivityContextGuidProperty );

            Debug.Assert( scope != Guid.Empty );

            byte[] serialized = Save( activity );

            Debug.Assert( serialized != null );
            Debug.Assert( serialized.Length > 0 );

            Dictionary<Guid, byte[]> scopes = null;

            if ( _scopes.ContainsKey( WorkflowEnvironment.WorkflowInstanceId ) == false )
            {
                scopes = new Dictionary<Guid, byte[]>();

                _scopes[ WorkflowEnvironment.WorkflowInstanceId ] = scopes;
            }
            else
            {
                scopes = _scopes[ WorkflowEnvironment.WorkflowInstanceId ];
            }

            scopes.Add( scope, serialized );
        }

        protected override Activity LoadWorkflowInstanceState( Guid instance )
        {
            Debug.Assert( _workflows != null );

            if ( _workflows.ContainsKey( instance ) == false )
            {
                throw new PersistenceException();
            }

            Activity deserialized = Load( _workflows[ instance ], null );

            Debug.Assert( deserialized != null );

            return deserialized;
        }

        protected override void SaveWorkflowInstanceState( Activity root, bool unlock )
        {
            WorkflowStatus status = WorkflowPersistenceService.GetWorkflowStatus( root );

            if ( status == WorkflowStatus.Completed || status == WorkflowStatus.Terminated )
            {
                _workflows.Remove( WorkflowEnvironment.WorkflowInstanceId );

                _scopes.Remove( WorkflowEnvironment.WorkflowInstanceId );

                _instanceTimers.Remove( WorkflowEnvironment.WorkflowInstanceId );
            }
            else
            {
                byte[] serialized = Save( root );

                Debug.Assert( serialized != null );
                Debug.Assert( serialized.Length > 0 );

                _workflows[ WorkflowEnvironment.WorkflowInstanceId ] = serialized;

                TimerEventSubscriptionCollection timers = (TimerEventSubscriptionCollection) root.GetValue( TimerEventSubscriptionCollection.TimerCollectionProperty );

                TimerEventSubscription subscription = timers.Peek();

                if ( subscription != null )
                {
                    TimeSpan timeDifference = subscription.ExpiresAt - DateTime.UtcNow;

                    if ( timeDifference > MaxInterval )
                    {
                        timeDifference = MaxInterval;
                    }
                    else if ( timeDifference < TimeSpan.Zero )
                    {
                        timeDifference = TimeSpan.Zero;
                    }

                    _instanceTimers.Add( WorkflowEnvironment.WorkflowInstanceId, new Timer( ReloadWorkflow, subscription.WorkflowInstanceId, timeDifference, new TimeSpan( -1 ) ) );
                }
            }
        }

        protected override bool UnloadOnIdle( Activity activity )
        {
            return true;
        }

        protected override void UnlockWorkflowInstanceState( Activity rootActivity )
        {
            // not supported
        }

        private void ReloadWorkflow( object id )
        {
            Timer toDispose = null;

            Guid guid = (Guid) id;

            if ( _instanceTimers.TryGetValue( guid, out toDispose ) )
            {
                _instanceTimers.Remove( guid );

                toDispose.Dispose();
            }

            this.Runtime.GetWorkflow( guid );
        }

        private Activity Load( byte[] bytes, Activity outer )
        {
            Debug.Assert( bytes != null );

            return WorkflowPersistenceService.RestoreFromDefaultSerializedForm( bytes, outer );
        }

        private byte[] Save( Activity activity )
        {
            Debug.Assert( activity != null );

            return WorkflowPersistenceService.GetDefaultSerializedForm( activity );
        }
    }
}
